﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BoatNGoat.Server;
using SharpDX;
using SharpDX.Toolkit.Graphics;

namespace BoatNGoat
{
    public class RenderingException : Exception
    {
        public RenderingException()
        {
        }

        public RenderingException(string message) : base(message)
        {
        }

        public RenderingException(string message, Exception inner) : base(message, inner)
        {
        }
    }

    public class RenderingEngine
    {
        //private readonly Dictionary<int, SpriteSheet> _spriteSheets = new Dictionary<int, SpriteSheet>();
        private readonly Dictionary<int, Sprite> _sprites = new Dictionary<int, Sprite>();
        private readonly Dictionary<int, SpriteAnimationState> _spriteStates = new Dictionary<int, SpriteAnimationState>();
        private readonly LocalSimulation _simulation;
        private readonly GraphicsDevice _graphicsDevice;
        private readonly MapView _mapView;

        public RenderingEngine(GraphicsDevice graphicsDevice, LocalSimulation simulation)
        {
            _simulation = simulation;
            _graphicsDevice = graphicsDevice;
            _mapView = new MapView(_simulation.GetMap());
        }

        public Camera Camera { get { return _mapView.Camera; }
        }

        public void SetTileSet(SpriteSheet tileSet)
        {
            _mapView.TileSet = tileSet;
        }

        public void SetSpriteForEntityType(int entityType, Sprite sprite)
        {
            _sprites[entityType] = sprite;
        }

        public void Draw()
        {
            IEnumerable<IMobile> goats = _simulation.GetGoats();
            Player player = _simulation.GetLocalPlayer();
            
            SpriteBatch spriteBatch = new SpriteBatch(_graphicsDevice);

            spriteBatch.Begin(SpriteSortMode.Deferred, _graphicsDevice.BlendStates.NonPremultiplied);
            _mapView.Draw(spriteBatch, spriteBatch.GraphicsDevice.Viewport);
            DrawSprites(goats, spriteBatch);
            DrawSprites(new[]{player}, spriteBatch);
            spriteBatch.End();
        }

        private Vector2 ScreenCentre(ViewportF viewport)
        {
            return new Vector2(
                x: (float)(viewport.X + 0.5 * viewport.Width),
                y: (float)(viewport.Y + 0.5 * viewport.Height)
            );
        }
        
        /// <summary>
        /// Draw a single entity to an existing <see cref="SharpDX.Toolkit.Graphics.SpriteBatch"/>
        /// </summary>
        /// <param name="entities">The enumerable of entities to draw to the screen</param>
        /// <param name="spriteBatch">The existing <see cref="SharpDX.Toolkit.Graphics.SpriteBatch"/> that the entities will be appended to.</param>
        public void DrawSprites(IEnumerable<IMobile> entities, SpriteBatch spriteBatch)
        {
            Vector2 screenCentre = ScreenCentre(spriteBatch.GraphicsDevice.Viewport);
            foreach (var entity in entities)
            {
                // If the entity type doesn't seem to have a sprite sheet assigned, die.
                if (!_sprites.ContainsKey(entity.EntityType))
                    throw new RenderingException("Sprite not found for entity type: " + entity.EntityType);

                Sprite sprite = _sprites[entity.EntityType];
                SpriteAnimationState animationState = HandleAnimation(entity);

                Vector2 screenPosition = _mapView.MapToScreen(entity.Position, screenCentre);

                screenPosition.X = (int) Math.Round(screenPosition.X);
                screenPosition.Y = (int) Math.Round(screenPosition.Y);
                
                DrawingRectangle rect = sprite.GetDrawingRectangle(animationState.AnimationName, animationState.CurrentFrame);
                spriteBatch.Draw(sprite.SpriteSheet.Texture, screenPosition, rect, Color.White, 0.0f,
                                 sprite.SpriteSheet.Origin, Vector2.One*(float)_mapView.Camera.Zoom, SpriteEffects.None, 0f);
            }
            
        }

        private SpriteAnimationState HandleAnimation(IMobile entity)
        {
            switch (entity.EntityType)
            {
                case 0:
                    return HandlePlayerAnimation(entity);
                case 1:
                    return HandleGoatAnimation(entity);
                default:
                    throw new ArgumentException("EntityType not valid");
            }
        }

        private SpriteAnimationState HandleGoatAnimation(IMobile entity)
        {
            SpriteAnimationState animationState;
            if (!_spriteStates.TryGetValue(entity.EntityId, out animationState))
            {
                animationState = new SpriteAnimationState("walkleft", 0, 2, 1200);
                _spriteStates[entity.EntityId] = animationState;
            }

            Vector2 velocity = entity.Velocity;
            float absX = Math.Abs(velocity.X);
            float absY = Math.Abs(velocity.Y);

            // If the goat is not moving, do no animation.
            if (absX < 0.001 && absX > -0.001 && absY < 0.001 && absY > -0.001)
                return animationState;

            // If the horizontal movement is equal to or greater than the vertical one, use a left/right animation.
            string previousAnimation = animationState.AnimationName;
            string nextAnimation;
            if (absX >= absY)
            {
                nextAnimation = velocity.X > 0 ? "walkright" : "walkleft";
            }
            else
            {
                nextAnimation = velocity.Y > 0 ? "walkup" : "walkdown";
            }

            // If the animation hasn't changed over the update
            if (previousAnimation.Equals(nextAnimation, StringComparison.OrdinalIgnoreCase))
                animationState.Forwards(_mapView.ScreenVector(entity.Velocity).Length());
            else
            {
                animationState.SetAnimation(nextAnimation, 0, 2, 1200);
            }

            return animationState;
        }

        private SpriteAnimationState HandlePlayerAnimation(IMobile entity)
        {
            Sprite sprite = _sprites[entity.EntityType];

            SpriteAnimationState animationState;
            if (!_spriteStates.TryGetValue(entity.EntityId, out animationState))
            {
                animationState = new SpriteAnimationState("walkleft", 0, 2, 800);
                _spriteStates[entity.EntityId] = animationState;
            }

            Vector2 velocity = entity.Velocity;
            float absX = Math.Abs(velocity.X);
            float absY = Math.Abs(velocity.Y);

            // If the goat is not moving, do no animation.
            if (absX < 0.001 && absX > -0.001 && absY < 0.001 && absY > -0.001)
                return animationState;

            // If the horizontal movement is equal to or greater than the vertical one, use a left/right animation.
            string previousAnimation = animationState.AnimationName;
            string nextAnimation;
            if (absX >= absY)
            {
                nextAnimation = velocity.X > 0 ? "walkright" : "walkleft";
            }
            else
            {
                nextAnimation = velocity.Y > 0 ? "walkup" : "walkdown";
            }

            // If the animation hasn't changed over the update
            if (previousAnimation.Equals(nextAnimation, StringComparison.OrdinalIgnoreCase))
                animationState.Forwards(_mapView.ScreenVector(entity.Velocity).Length());
            else
            {
                int numberOfFramesInAnimation = sprite.GetAnimation(nextAnimation).AnimationFrames.Count;
                animationState.SetAnimation(nextAnimation, 0, numberOfFramesInAnimation, 800);
            }

            return animationState;
        }
    }
}
